home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 August: Tool Chest / Dev.CD Aug 00 TC Disk 2.toast / pc / sample code / sound / soundapp / soundunit.c < prev    next >
Encoding:
Text File  |  2000-06-23  |  53.3 KB  |  1,628 lines

  1. /*
  2.     File:        SoundUnit.c
  3.  
  4.     Contains:    This is the "new" SoundUnit which adds some new features.
  5.                     • Some knowledge of the new Sound Manager is present
  6.                           in areas that were work-arounds for old Sound Manager bugs
  7.                     • Conversion to MPW 3.2 was established (with some amount of pain)
  8.                     • Added the new Sound Manager error strings
  9.                     • Checking of the sound header for supported encode values
  10.                     • The amp value is ignored (never used) in a freqDurationCmd
  11.                     • Added functions to test sound hardware/software features
  12.                           such as stereo, MACE, Sound Input
  13.  
  14.     Written by:     
  15.  
  16.     Copyright:    Copyright © 1994-1999 by Apple Computer, Inc., All Rights Reserved.
  17.  
  18.                 You may incorporate this Apple sample source code into your program(s) without
  19.                 restriction. This Apple sample source code has been provided "AS IS" and the
  20.                 responsibility for its operation is yours. You are not permitted to redistribute
  21.                 this Apple sample source code as "Apple sample source code" after having made
  22.                 changes. If you're going to re-distribute the source, we require that you make
  23.                 it clear in the source that the code was descended from Apple sample source
  24.                 code, but that you've made changes.
  25.  
  26.     Change History (most recent first):
  27.                 7/29/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
  28.                 
  29.  
  30. */
  31.  
  32. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  33. //includes
  34. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  35.  
  36. #include <Errors.h>
  37. #include <FixMath.h>
  38. #include <fp.h>
  39. #include <Gestalt.h>
  40. #include <LowMem.h>
  41. #include <Memory.h>
  42. #include <MixedMode.h>
  43. #include <Resources.h>
  44.  
  45. #include <limits.h>
  46. #include <stddef.h>
  47.  
  48. #include <Sound.h>
  49. #include <SoundInput.h>
  50. #include "SoundUnit.h"
  51.  
  52. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  53. // private constants
  54. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  55.  
  56. enum {
  57.     kMaxChannels    = 4,                    //maximum number of supported channels
  58.  
  59.     kSoundComplete    = 0x1234                //flag for callBackCmd
  60. };
  61.  
  62. /*
  63. These are used as flags in the sound channel to determine the state
  64. of that channel.
  65. */
  66. enum {
  67.     kChanFreeState    = 0,                //channel is not in use
  68.     kChanCompleteState                    //channel has completed
  69. };
  70.  
  71. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  72. // macros
  73. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  74.  
  75. #if GENERATINGCFM
  76. #define CreateRoutineDescriptor(info, proc)                                    \
  77.  RoutineDescriptor g##proc##RD = BUILD_ROUTINE_DESCRIPTOR(info, proc)
  78.  
  79. #define GetRoutineAddress(proc)    (&g##proc##RD)
  80.  
  81. #else
  82. #define GetRoutineAddress(proc)    proc
  83. #endif
  84.  
  85. // this belongs in LowMem.h
  86. extern unsigned short LMGetSoundActive( void )
  87.     TWOWORDINLINE( 0x1038, 0x027E ); /* MOVE.B $027E, D0 */
  88.  
  89. // For Power Macs, there is no Sound Driver and therefore SoundActive in
  90. // low memory should not be a problem. This is sometimes set by third parties
  91. // that are writting directly to the hardware, and also set by the old
  92. // sound driver when it is active. When this is happening, the Sound Manager
  93. // cannot work. So we check the low memory global to be less than 0. But
  94. // for Power Mac builds, we just return 0.
  95.  
  96. #if USESROUTINEDESCRIPTORS
  97. #define SoundDriverActive() false
  98. #else
  99. #define SoundDriverActive() (LMGetSoundActive() < 0)
  100. #endif
  101.  
  102. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  103. // private types
  104. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  105.  
  106. /*
  107. This is an element of a globals array that is used to keep track of
  108. sound channels created by the sound unit.  It contains all the useful
  109. information associated with the channel.  I keep the 'snd ' resource
  110. handle associated to this channel too.  This allows me to dispose of
  111. the data once the channel has completed its duties.
  112. */
  113.  
  114. struct ChanInfo {
  115.     SndChannelPtr    chan;
  116.     SndListHandle    dataHandle;
  117.     short            chanState;
  118.     short            chanType;
  119. };
  120. typedef struct ChanInfo ChanInfo;
  121. typedef ChanInfo *ChanInfoPtr;
  122.  
  123.  
  124. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  125. // private prototypes
  126. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  127.  
  128. void FreeChan(ChanInfoPtr info);
  129. OSErr ChanAvailable(ChanInfoPtr info);
  130. Boolean CompatibleChan(ChanInfoPtr info);
  131. OSErr InstallSampleSnd(ChanInfoPtr info, SndListHandle sndHandle);
  132. OSErr NewWaveChan(ChanInfoPtr info, short init);
  133.  
  134. pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd);
  135. Boolean IsMyChan(SndChannelPtr chan);
  136. OSErr SndDataAvailable(SndListHandle sndHandle);
  137. ModRef GetSynthInfo(SndListHandle sndHandle);
  138. Boolean SupportedSH(SoundHeaderPtr sndPtr);
  139. OSErr ReleaseSynch(SndChannelPtr chan);
  140. OSErr Synch1Chan(SndChannelPtr chan, short count);
  141. OSErr SynchChans(SndChannelPtr chan1, SndChannelPtr chan2, SndChannelPtr chan3, SndChannelPtr chan4);
  142.  
  143.  
  144. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  145. //globals (The “g” prefix is used to emphasize that a variable is global.)
  146. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  147.  
  148. /*
  149. This is the global array of sound channel information.
  150. */
  151.  
  152.     ChanInfoPtr        gChanInfo;
  153.  
  154. /*
  155. gSoundMgrVersion is used to determine if the application is running
  156. with the old Sound Manager.  This was shipped prior to System 6.0.6.
  157. This flag is setup in the InitSoundUnit routine and used by the rest of this
  158. source file. There are workarounds to problems based on this condition.
  159. */
  160.     short            gSoundMgrVersion;
  161.  
  162. // allocate the RoutineDescriptors for Power Mac toolbox calls
  163. #if GENERATINGCFM
  164. CreateRoutineDescriptor(uppSndCallBackProcInfo, DoCallBack);
  165. #endif
  166.  
  167. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  168. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  169. /*
  170. This is a dummy routine to allow the application to unload this segment.
  171. */
  172.  
  173. #pragma segment SoundUnit
  174. pascal void _SoundUnit(void)
  175. {
  176. }
  177.  
  178. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  179. /*
  180. Create storage for each of the four channels (4 * 1064 bytes) and
  181. initialize them.  If any of the channels cannot be allocated, return an
  182. error.  If the user only wants one channel, then we could just allocate
  183. one instead of four.  These channels are used at interrupt time.
  184.  
  185. VERSION 1.1: Added the new Sound Manager flag, gNewSndMgr.  First I have to
  186. test if the _SoundDispatch trap is available, since SndManagerVersion is
  187. a selector for _SoundDispatch.  This this trap isn't available, then calling
  188. SndManagerVersion would result in an unimplemented instruction.  If an error
  189. is returned, it is the old Sound Manager.  If it is zero, then the call
  190. is available (via-MIDI Mgr?) but it isn't the new Sound Manager.  Greater
  191. than zero means it is the new Sound Manager.
  192.  
  193. VERSION 1.2: Only supports Sound Manager 2.0 or later. I would personally
  194. require version 3.0 or later in my own products.
  195. */
  196.  
  197. #pragma segment Initialize
  198. pascal OSErr InitSoundUnit(void)
  199. {
  200.     OSErr        theErr;
  201.  
  202.     // check if the supported Sound Manager is present, this is the one
  203.     // that has Sound Input and supports the _SoundDispatch trap
  204.  
  205.     theErr = noErr;
  206.     gSoundMgrVersion = GetSoundMgrVersion();
  207.     if (gSoundMgrVersion > 1)
  208.     {
  209.         gChanInfo = (ChanInfoPtr)NewPtrClear(sizeof(ChanInfo[kMaxChannels]));
  210.         if (gChanInfo == nil)
  211.             theErr = MemError();
  212.     }
  213.     return(theErr);
  214. }
  215.  
  216. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  217. /*
  218. To determine if MACE is available, there are two case.  If the new Sound Manager
  219. is running then MACE may be built in.  This is easy enough to test for by calling
  220. the new trap call.  Otherwise I have to test for the presence of the MACE
  221. snth resources.  The old MACE snths could only be present if the user ran the
  222. MACE Installer Scripts which are available from APDA. This was only supported
  223. on some versions of System 6.0.x.
  224.  
  225. VERSION 1.2: Forget about dealing with MACE prior to Sound Manager 2, it was
  226. just a hack and didn't work on all machines.
  227. */
  228.  
  229. #pragma segment Main
  230. pascal Boolean HasMACE(void)
  231. {
  232.     NumVersion version;
  233.     Boolean result;
  234.  
  235.     result = false;
  236.     if (GetSoundMgrVersion() > 1)
  237.     {
  238.         version = MACEVersion();
  239.         //result = (version.majorRev > 0);            //is the built-in MACE here?
  240.         
  241.         result = (*(UInt8*)&version > 0);            // added by MW  (see comments, above)
  242.     }
  243.     return (result);
  244. }
  245.  
  246. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  247. /*
  248. This is used to determine if Sound Input is available. The Gestalt flag
  249. gestaltHasSoundInputDevice can be used to determine if an input device is
  250. available. This flag did not exist prior to System 7. The other related
  251. flag gestaltBuiltInSoundInput can only be used to determine if the machine
  252. has built-in sound input hardware, so don't be mislead. In System 6.0.7 you
  253. would have to use SPBGetIndexdDevice to find the fist one.  If this returns
  254. noErr then Sound Input is available. Also, the icon handle returned by this
  255. call has to be disposed of by you the caller.
  256.  
  257. VERSION 1.1:  This is the external routine for users to determine if
  258. sound input is available.
  259.  
  260. VERSION 1.2: Use the Gestalt method since we know that we're running Sound
  261. Manager 2 or later which supports the gestaltHasSoundInputDevice flag.
  262. */
  263.  
  264. #pragma segment Main
  265. pascal Boolean HasSoundInput(void)
  266. {
  267.     long        response;
  268.     OSErr        theErr;
  269.     Boolean        result;
  270.  
  271.     theErr = Gestalt(gestaltSoundAttr, &response);
  272.     if ( (theErr == noErr) && (response & (1<<gestaltHasSoundInputDevice)) )
  273.         result = true;
  274.     else
  275.         result = false;
  276.  
  277.     return (result);
  278. }
  279.  
  280. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  281. /*
  282. VERSION 1.1:  This is the external routine for users to determine if
  283. sound input is available. Sound Manager 3.0 can always support stereo,
  284. even on mono hardware.
  285. */
  286.  
  287. #pragma segment Main
  288. pascal Boolean HasStereoSupport(void)
  289. {
  290.     long response;
  291.     OSErr theErr;
  292.     Boolean result;
  293.  
  294.     theErr = Gestalt(gestaltSoundAttr, &response);
  295.     if ( (theErr == noErr) && (response & (1<<gestaltStereoCapability)) )
  296.         result = true;
  297.     else
  298.         result = false;
  299.     return (result);
  300. }
  301.  
  302. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  303. /*
  304. This will return the version of the sound manager that is currently running.
  305. A special case is necessary because the first sound manager that reported
  306. its version didn't have the SndSoundManagerVersion() implemented. So that means
  307. older versions are really 1.0, and the first oldest version returned by
  308. SndSoundManagerVersion is 2.0. Anything older than 2.0 has a few problems.
  309. */
  310.  
  311. pascal short GetSoundMgrVersion(void)
  312. {
  313.     NumVersion version;
  314.     long response;
  315.     short result;
  316.     OSErr theErr;
  317.  
  318.     result = 1;
  319.     theErr = Gestalt(gestaltSoundAttr, &response);
  320.     if ( (theErr == noErr) && (response & (1<<gestaltSoundIOMgrPresent)) )
  321.     {
  322.         version = SndSoundManagerVersion();
  323.         //result = version.majorRev;
  324.         
  325.         result = *(UInt8*)&version;            // added by MW (see comments, above)
  326.     }
  327.     return(result);
  328. }
  329.  
  330. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  331. /*
  332. This routine can be called to determine if the sound has completed.  When
  333. this is true, the data can be disposed of.  It is set by the sound
  334. channel's completion routine.  It is placed in the Main segment because
  335. it is called by the event loop.
  336. */
  337.  
  338. #pragma segment Main
  339. pascal Boolean HasSoundCompleted(void)
  340. {
  341.     short        i;
  342.     Boolean        result;
  343.  
  344.     result = true;                            //assume true, then check for busy channels
  345.     for (i = 0; i < kMaxChannels; i++)
  346.     {
  347.         // if we have a channel and it is not completed, then we're still busy playing
  348.         if ((gChanInfo[i].chan != nil) && (gChanInfo[i].chanState != kChanCompleteState))
  349.         {
  350.             result = false;
  351.             break;
  352.         }
  353.     }
  354.     return(result);
  355. }
  356.  
  357. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  358. /*
  359. This routine can be called at any time.  It will return true when the
  360. SoundUnit has an open channel.  This can be can considered the same as
  361. sound being active.  As long as the channel is open, no other channels can
  362. be opened.  It is placed in the Main segment because it is called by the
  363. event loop.
  364. */
  365.  
  366. #pragma segment Main
  367. pascal Boolean HasChannelOpen(void)
  368. {
  369.     short        i;
  370.     Boolean        result;
  371.  
  372.     result = false;
  373.     for (i = 0; i < kMaxChannels; i++)
  374.     {
  375.         if (gChanInfo[i].chan != nil)
  376.         {
  377.             result = true;
  378.             break;
  379.         }
  380.     }
  381.  
  382.     return(result);
  383. }
  384.  
  385. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  386. /*
  387. Given a channel and timbre(sounds like "tom burr"), this will adjust the
  388. tone quality of the square wave synthesizer.  Changing the tone can only be done
  389. before playing a square wave.  On a Mac with the Apple Sound Chip, this can be
  390. done in real time while a note is playing.  But, since there's no
  391. supported method for determining if the ASC is available I have to assume
  392. that it's not.  I use the immediate flag to determine if the user wants to
  393. change the timbre now, or queue the command.  If the queue is full, it
  394. will wait for the command to be accepted.
  395.  
  396. BUG NOTE: There is a bug in the Sound Manager running on the Mac Plus or
  397. SE where sending a timbreCmd with a timbre of 255(a legal value)will
  398. crash.  The difference between 254 and 255 isn't audible, so I only allow
  399. a maximum of 254 in any case.
  400. */
  401.  
  402. #pragma segment SoundUnit
  403. pascal OSErr SetSquareWaveTimbre(SndChannelPtr squareChan, short timbre, Boolean immediate)
  404. {
  405.     SndCommand theCmd;
  406.     OSErr      result;
  407.  
  408.     if (timbre > 254)
  409.         timbre = 254;
  410.  
  411.     theCmd.cmd = timbreCmd;
  412.     theCmd.param1 = timbre;
  413.     theCmd.param2 = 0;
  414.  
  415.     if (immediate)
  416.         result = SndDoImmediate(squareChan, &theCmd);
  417.     else
  418.         result = SndDoCommand(squareChan, &theCmd, kWait);
  419.  
  420.     return(result);
  421. }
  422.  
  423. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  424. /*
  425. Given a channel and note information, this will place the note into the
  426. channel's queue.  The note contains the amplitude and note value.  It is a
  427. four byte parameter with the high byte containing the amplitude.  I use
  428. SndDoCommand with the noWait flag set to wait for the channel to except
  429. the command in the case the queue is currently full.
  430. */
  431.  
  432. #pragma segment SoundUnit
  433. pascal OSErr SendNote(SndChannelPtr chan, short duration, long note)
  434. {
  435.     SndCommand theCmd;
  436.  
  437.     theCmd.cmd = freqDurationCmd;
  438.     theCmd.param1 = duration;
  439.     theCmd.param2 = note;
  440.  
  441.     return(SndDoCommand(chan, &theCmd, kWait));
  442. }
  443.  
  444. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  445. /*
  446. Given a channel, this will place a quietCmd into the channel's queue.  I
  447. use SndDoCommand with the noWait flag set to wait for the channel to
  448. except the command in the case it is currently full.
  449.  
  450. BUG NOTE: A sequence of notes and rests will not work unless quietCmds
  451. are between them.  Rests have to be made quiet before they rest, if that
  452. makes any more sense.  A freqDurationCmd will loop, causing the sound in progress
  453. to continue, until a quietCmd is received.
  454. */
  455.  
  456. #pragma segment SoundUnit
  457. pascal OSErr SendQuiet(SndChannelPtr chan, Boolean immediate)
  458. {
  459.     SndCommand theCmd;
  460.     OSErr      result;
  461.  
  462.     theCmd.cmd = quietCmd;
  463.     theCmd.param1 = 0;
  464.     theCmd.param2 = 0;
  465.  
  466.     if (immediate)
  467.         result = SndDoImmediate(chan, &theCmd);
  468.     else
  469.         result = SndDoCommand(chan, &theCmd, kWait);
  470.  
  471.     return(result);
  472. }
  473.  
  474. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  475. /*
  476. Given a channel and duration, this will place the rest into the channel's
  477. queue.  Before sending a rest a quietCmd is needed.  Rests don't work
  478. unless you tell the Sound Manager to be quiet too.  I use SndDoCommand
  479. with the noWait flag set to wait for the channel to except the command in
  480. the case it is currently full.
  481. */
  482.  
  483. #pragma segment SoundUnit
  484. pascal OSErr SendRest(SndChannelPtr chan, short duration)
  485. {
  486.     SndCommand theCmd;
  487.     OSErr      theErr;
  488.  
  489.     theErr = SendQuiet(chan, kWait);
  490.  
  491.     if (theErr == noErr) {
  492.         theCmd.cmd = restCmd;
  493.         theCmd.param1 = duration;
  494.         theCmd.param2 = 0;
  495.  
  496.         theErr = SndDoCommand(chan, &theCmd, kWait);
  497.     }
  498.  
  499.     return(theErr);
  500. }
  501.  
  502. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  503. /*
  504. Test if the channel is free, and if not then call SndDisposeChannel.
  505. This will release the synthesizer(snth resource)code and the
  506. required hardware.  If we didn't do this, no other channels would
  507. work.  I also test myChan for having a snd resource attached to the
  508. channel.  If so, then I mark it as purgeable and reset the data to nil.
  509.  
  510. BUG NOTE: Calling SndDisposeChannel while or immediately after playing
  511. a sequence of notes would often hang/crash a non-Apple Sound Chip based Mac.
  512. Issuing a quietCmd first kept the Sound Manager happy and my Mac from
  513. crashing.
  514. */
  515.  
  516. #pragma segment SoundUnit
  517. void FreeChan(ChanInfoPtr info)
  518. {
  519.     OSErr      theErr;
  520.  
  521.     if (info->chan != nil) {
  522.         theErr = SendQuiet(info->chan, !kWait);  // ignore error
  523.         theErr = SndDisposeChannel(info->chan, !kWait);
  524.         info->chan = nil;
  525.         info->chanState = kChanFreeState;
  526.     }
  527.  
  528.     if (info->dataHandle != nil) {
  529.         HUnlock((Handle)info->dataHandle);
  530.         HPurge((Handle)info->dataHandle);
  531.         info->dataHandle = nil;
  532.     }
  533. }
  534.  
  535. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  536. /*
  537. This routine is called by an application that established a sound to be
  538. played asynchronously.  This is in effect, the routine to be used after
  539. the completion routine has been called.  The application should call this
  540. once HasSoundCompleted() returns true.  In the case the application is using
  541. multiple channels, we will only free a channel once it has been marked as
  542. kChanCompleteState.
  543. */
  544.  
  545. #pragma segment SoundUnit
  546. pascal void DoSoundComplete(void)
  547. {
  548.     short        i;
  549.  
  550.     for (i = 0; i < kMaxChannels; i++)
  551.     {
  552.         if (gChanInfo[i].chanState != kChanFreeState)
  553.             FreeChan(&gChanInfo[i]);
  554.     }
  555. }
  556.  
  557. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  558. /*
  559. This is the routine that will force all channels to be released  This is used by
  560. all routines just before opening a new channel to force all channels to be disposed.
  561. */
  562.  
  563. #pragma segment SoundUnit
  564. pascal void FreeAllChans(void)
  565. {
  566.     short        i;
  567.  
  568.     for (i = 0; i < kMaxChannels; i++)
  569.         FreeChan(&gChanInfo[i]);
  570. }
  571.  
  572. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  573. /*
  574. This is the final routine to be called by the application when it is has
  575. finished using this SoundUnit.  This will dispose of all the channels and
  576. memory used by this SoundUnit.
  577. */
  578.  
  579. #pragma segment SoundUnit
  580. pascal void FreeSoundUnit(void)
  581. {
  582.     FreeAllChans();
  583.     DisposePtr((Ptr)gChanInfo);
  584. }
  585.  
  586. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  587. /*
  588. This will be called at interrupt time by the Sound Manager when it
  589. receives a callBackCmd.  I use the second parameter of the command to hold
  590. my application's A5 reference.  I first set up A5 so that I can access my
  591. globals.  I mark the given channel as being complete.  This lets the
  592. application know that the callBackCmd has been processed.  The callBackCmd
  593. can be used for other purposes, and the first parameter of the command
  594. could be a flag to a more extensive routine.  Synchronizing the application
  595. with the channel is possible with this method.
  596.  
  597. WARNING: This routine MUST be resident in memory and cannot make a call
  598. to a non-resident segment.  I put this into the Main segment because of
  599. this.
  600.  
  601. BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
  602. 'snd '.  A bogus callBackCmd is placed into the queue immediately after
  603. the bufferCmd used to play the sound.  This bogus callBackCmd will cause
  604. my callBackProc to be called when I wasn't expecting it.  I have been
  605. using the command's second parameter to contain my A5 address.  If I'm
  606. given a bogus callBackCmd, it would be really bad to set A5 address to
  607. this bogus parameter in the command.  I found that the bogus callBackCmd
  608. contains the handle to the 'snd ' passed in to _SndPlay.  I also found
  609. that param1 contains the handle's state bits(results of HGetState).  To
  610. work with this bug I set my real callBackCmd's param1 to a specific value
  611. when I installed it into the queue.  See the SoundComplete routine.  Then
  612. I test the callBackCmd to make sure I'm dealing with the real one.
  613.  
  614. VERSION 1.2: No longer using A5 globals.  Get the address that we need
  615. in param2, instead of passing in our application's A5 address.
  616. */
  617.  
  618. #pragma segment Main
  619. pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd)
  620. {
  621. #pragma unused (chan)
  622.     ChanInfoPtr        info;
  623.  
  624.     if (theCmd->param1 == kSoundComplete)         // if it's my callBackCmd
  625.     {
  626.         info = (ChanInfoPtr)theCmd->param2;
  627.         info->chanState = kChanCompleteState;    // this channel is done
  628.     }
  629. }
  630.  
  631. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  632. /*
  633. I use this to install the callBackCmd into the given channel.  I need to
  634. pass in our A5 along with the command so that the callBack routine can
  635. access my globals.  I also wait until the channel is ready for another
  636. command in the case of the channel being full.  Once the Sound Manager
  637. calls my call back procedure I will dispose of the channel.  So, this is
  638. the last sound command to be sent to a channel.  I pass to the call back
  639. A5 in the second parameter of the callBackCmd.  Refer to Tech Note #208.
  640.  
  641. VERSION 1.2: No longer using A5 globals.  Get the address that we need
  642. in param2, instead of passing in our application's A5 address.
  643. */
  644.  
  645. #pragma segment SoundUnit
  646. pascal OSErr SoundComplete(SndChannelPtr chan)
  647. {
  648.     SndCommand    theCmd;
  649.     OSErr        result;
  650.     short        i;
  651.  
  652.     theCmd.cmd = callBackCmd;
  653.     theCmd.param1 = kSoundComplete;
  654.     theCmd.param2 = 0;                            // initialize value
  655.  
  656.     for (i = 0; i < kMaxChannels; i++)
  657.     {
  658.         if (gChanInfo[i].chan == chan)
  659.         {
  660.             theCmd.param2 = (long)(&gChanInfo[i]);
  661.             break;
  662.         }
  663.     }
  664.     if (theCmd.param2 == 0)
  665.         result = badChannel;                    // channel wasn't one of ours
  666.     else
  667.         result = SndDoCommand(chan, &theCmd, kWait);
  668.  
  669.     return(result);
  670. }
  671.  
  672. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  673. /*
  674. This routine will test the given channel to see if it will really produce
  675. sound.  The Sound Manager in System 6.0x will return noErr even if the
  676. channel isn't going to work.  In the future the Sound Manager will return
  677. the proper error.  Until then, I use this routine to determine this for me.
  678. There can only be a single channel at any time, unless I have the wave
  679. table synthesizer open.  This will allow four channels.  Channels have
  680. a pointer to the next channel, and if this is not nil I suspect the
  681. given channel will not work.  I test the given channel for being a
  682. wave type, and if so I need to see if the other channels I've got are
  683. also wave type.  If it doesn't look like the channels is available, I
  684. return badChannel.
  685.  
  686. This routine assumes the channel passed in is the first channel allocated.
  687. Channels are held in a linked list, and the first one to be tested has
  688. to be the first one allocated.
  689.  
  690. BUG NOTE: If an application is not using the Sound Manager and instead
  691. uses the older Sound Driver, any given channel will fail.  Or if the other
  692. application does not release is channels, then my channels will not work.
  693. The most noticeable offender of this is HyperCard.  Friendly applications
  694. will dispose of their channels at suspend/resume times or ASAP.
  695.  
  696. VERSION 1.1: Added the new Sound Manager test.  The new Sound Manager will
  697. allow multiple sound channels, and returns proper error codes.
  698.  
  699. VERSION 1.2: This is not used since we only support Sound Manager 2.0 or later.
  700. */
  701.  
  702. #pragma segment SoundUnit
  703. OSErr ChanAvailable(ChanInfoPtr info)
  704. {
  705.     OSErr        result;
  706.     short        i;
  707.  
  708.     if (gSoundMgrVersion >= 3)
  709.         return(noErr);
  710.  
  711.     result = noErr;
  712.  
  713.     if (SoundDriverActive())                        // check for sound driver
  714.         return(notEnoughHardwareErr);
  715.  
  716.     if (info->chan->nextChan != nil)                // looks bad
  717.     {
  718.         result = badChannel;                          // prepart to fail
  719.         if (info->chanType == waveTableSynth)        // last attempt
  720.         {
  721.             if (IsMyChan(info->chan->nextChan))
  722.             {
  723.                 for (i = 0; i < kMaxChannels; i++)
  724.                 {
  725.                     if (! (CompatibleChan(&gChanInfo[i])))
  726.                         break;
  727.                     result = noErr;                  // got lucky
  728.                 }
  729.             }
  730.         }
  731.     }
  732.     return(result);
  733. }
  734.  
  735. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  736. /*
  737. This is a utility routine that works with ChanAvailable.  It checks to
  738. see if the given channel is one of the four channels we opened.  If it
  739. is, IsMyChan returns true.  If it isn't, false is returned.
  740. */
  741.  
  742. #pragma segment SoundUnit
  743. Boolean IsMyChan(SndChannelPtr chan)
  744. {
  745.     short        i;
  746.     Boolean        result;
  747.  
  748.     result = false;
  749.     for (i = 0; i < kMaxChannels; i++)
  750.     {
  751.         if (gChanInfo[i].chan == chan)
  752.         {
  753.             result = true;
  754.             break;
  755.         }
  756.     }
  757.     return(result);
  758. }
  759.  
  760. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  761. /*
  762. This is a utility routine that works with ChanAvailable.  It returns
  763. true if the given channel is either a wave type of channel.  It also
  764. returns true if the channel is free.  Otherwise, it returns false.
  765. */
  766.  
  767. #pragma segment SoundUnit
  768. Boolean CompatibleChan(ChanInfoPtr info)
  769. {
  770.     Boolean        result;
  771.  
  772.     if (    (info->chanType == waveTableSynth)        // wave or..
  773.         ||    (info->chanState == kChanFreeState) )    // free chan
  774.  
  775.         result = true;
  776.     else
  777.         result = false;
  778.  
  779.     return(result);
  780. }
  781.  
  782. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  783. /*
  784. Given a resource handle, this will attempt to load it into memory.  If the
  785. data is not available, then return an error.
  786. */
  787.  
  788. #pragma segment SoundUnit
  789. OSErr SndDataAvailable(SndListHandle sndHandle)
  790. {
  791.     OSErr result;
  792.  
  793.     result = noErr;
  794.     if (sndHandle != nil) {
  795.         LoadResource((Handle)sndHandle);
  796.         if (*sndHandle == nil)
  797.             result = nilHandleErr;            // master pointer is nil
  798.     }
  799.     else
  800.         result = nilHandleErr;                // user passed a nil handle
  801.     return(result);
  802. }
  803.  
  804. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  805. /*
  806. This is used to put the given sound resource into memory and hold it there.
  807. I also use a MoveHHi to keep the heap from being fragmented.  If this
  808. fails, then I return an error.  I dereference the handle and check if the
  809. master pointer is nil.  This would mean the data could not be loaded.
  810. */
  811.  
  812. #pragma segment SoundUnit
  813. pascal OSErr HoldSnd(SndListHandle sndHandle)
  814. {
  815.     OSErr result;
  816.  
  817.     if (sndHandle != nil) {
  818.         LoadResource((Handle)sndHandle);
  819.         if (*sndHandle == nil)
  820.             result = nilHandleErr;            // master pointer is nil
  821.         else {
  822.             result = noErr;
  823.             HLockHi((Handle)sndHandle);
  824.         }
  825.     }
  826.     else
  827.         result = nilHandleErr;                // user passed a nil handle
  828.     return(result);
  829. }
  830.  
  831. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  832. /*
  833. This routine will return the 'snth' resource ID specified by the sound.
  834. I use this to determine if the given sound will work with _SndPlay.
  835. This routine does not require the data to be in memory when called.  It
  836. also doesn't lock it down while looking for the information.
  837.  
  838. VERSION 1.1:  If no synth information is found in the snd then by default
  839. it is assumed to be for the squareWaveSynth.
  840. */
  841.  
  842. #pragma segment SoundUnit
  843. ModRef GetSynthInfo(SndListHandle sndHandle)
  844. {
  845.     SndListPtr        soundPtr;
  846.     OSErr            theErr;
  847.     ModRef            currSynth;
  848.  
  849.     currSynth.modNumber = kNoSynth;            //initialize to no synth, and no init
  850.     currSynth.modInit = 0;
  851.     theErr = SndDataAvailable(sndHandle);
  852.     if (theErr == noErr)
  853.     {
  854.         soundPtr = (*sndHandle);
  855.         if (soundPtr->format == firstSoundFormat)
  856.         {
  857.             if (soundPtr->numModifiers != 0)
  858.                 currSynth = soundPtr->modifierPart[0];
  859.             else
  860.                 currSynth.modNumber = squareWaveSynth;
  861.         }
  862.         else                                //snd is a format 2 for HyperCard
  863.             currSynth.modNumber = sampledSynth;
  864.     }
  865.     return(currSynth);
  866. }
  867.  
  868. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  869. /*
  870. This routine will cruise through the given snd resource  It will locate
  871. the sound data, if any, and return its type and offset into the resource.
  872. I prefer to return an offset instead of a pointer because I don't want
  873. to have the data locked in memory.  If I return an offset, the caller
  874. can decided when and if it wants the resource locked down to access the
  875. sound data.   The first step in finding this data is to determine if I'm
  876. looking at a format 1 or 2 type snd.  A type 2 is easy, but a type 1 will
  877. require me to find the number of snths specified and then to skip over
  878. each one including the init option.  Once this is done, I have a pointer
  879. to the number of commands in the snd.  When I've found the first one, I
  880. examine it to find out if it is a sound data command.  Being it's a sound
  881. resource, the command will also have its dataPointerFlag set.  Once I've
  882. found a command I'm looking for I return its type and offset, then get out
  883. of the do-while block.  Otherwise I go on to the next command.  All of this
  884. makes it possible to get the sound data for use as an instrument sound.
  885. Typically this will be a sampled sound.
  886. */
  887.  
  888. #pragma segment SoundUnit
  889. pascal long GetSndDataOffset(SndListHandle sndHandle, short *dataType, short *waveLength)
  890. {
  891.     Ptr                cruisePtr;
  892.     long            sndDataOffset;
  893.     short            synths;
  894.     short            howManyCmds;
  895.  
  896.     sndDataOffset = 0;                            // initialize to defaults
  897.     *dataType = kNoSynth;
  898.     *waveLength = 0;
  899.     if (sndHandle == nil)
  900.         return (sndDataOffset);                    // return no data
  901.  
  902.     if (*sndHandle != nil) {
  903.         if ((**sndHandle).format == firstSoundFormat) {
  904.             synths = (**sndHandle).numModifiers;
  905.             cruisePtr = (Ptr)&(**sndHandle).modifierPart;
  906.             cruisePtr += (sizeof(ModRef) * synths);
  907.         }
  908.         else
  909.             cruisePtr = (Ptr)&((**(Snd2ListHandle)sndHandle).numCommands);
  910.         howManyCmds = *(short *)cruisePtr;        // pointing at number of cmds
  911.         cruisePtr += sizeof(howManyCmds);
  912.  
  913.         // cruisePtr is now at the first sound command
  914.         // cruise all commands and find a soundCmd or bufferCmd
  915.         do {
  916.             switch (((SndCmdPtr)cruisePtr)->cmd) {
  917.  
  918.                 case soundCmd | dataOffsetFlag:
  919.                 case bufferCmd | dataOffsetFlag:
  920.                     *dataType = sampledSynth;
  921.                     sndDataOffset = ((SndCmdPtr)cruisePtr)->param2;
  922.                     howManyCmds = 0;            // done, get out of loop
  923.                     break;
  924.  
  925.                 case waveTableCmd | dataOffsetFlag:
  926.                     *dataType = waveTableSynth;
  927.                     *waveLength = ((SndCmdPtr)cruisePtr)->param1;
  928.                     sndDataOffset = ((SndCmdPtr)cruisePtr)->param2;
  929.                     howManyCmds = 0;            // done, get out of loop
  930.                     break;
  931.  
  932.                 default:                        // catch any other type of cmd
  933.                     cruisePtr += sizeof(SndCommand);
  934.                     howManyCmds -= 1;
  935.                     break;
  936.             }
  937.         } while (howManyCmds >= 1);                // done with all the commands
  938.     }
  939.     return(sndDataOffset);
  940. }
  941.  
  942. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  943. /*
  944. VERSION 1.1:  Check the given sound header as being supported by the
  945. running Sound Manager.  The encode fields of the header are tested.
  946. The standard encode always works.  The MACE compressed sound will only work if
  947. MACE is present.  A MACE sound can also be a stereo sound, which will only
  948. work on stereo hardware.  The expanded sound is for a stereo sound and/or
  949. 16bit samples and is only supported by Sound Manager 2.0 or later.
  950. If the sound is a stereo sound, then it requires stereo support.
  951.  
  952. VERSION 1.2: Support for Sound Manager 3 and beyond. Any compressed
  953. sound may work if Sound Manager 3.0 or later is present.
  954. */
  955.  
  956. #pragma segment SoundUnit
  957. Boolean SupportedSH(SoundHeaderPtr sndPtr)
  958. {
  959.     Boolean result;
  960.  
  961.     result = false;
  962.     switch (sndPtr->encode)
  963.     {
  964.  
  965.         case stdSH:
  966.             result = true;
  967.             break;
  968.  
  969.         case cmpSH:
  970.             if (gSoundMgrVersion < 3)
  971.             {
  972.                 //Sound Manager 2.0 will only support MACE compressed sound
  973.                 //and only stereo sound on stereo machines.
  974.  
  975.                 if (    (((CmpSoundHeaderPtr)sndPtr)->snthID == MACE3snthID)
  976.                      || (((CmpSoundHeaderPtr)sndPtr)->snthID == MACE6snthID) )
  977.                 {
  978.                     if (((CmpSoundHeaderPtr)sndPtr)->numChannels == 1)
  979.                         result = true;
  980.                     else
  981.                         result = HasStereoSupport();
  982.                 }
  983.             }
  984.             else
  985.                 result = true;                //Sound Manager 3 and later does it all
  986.             break;
  987.  
  988.         case extSH:            // first check for 8 bit sounds
  989.             if (gSoundMgrVersion < 3)
  990.             {
  991.  
  992.                 if (((ExtSoundHeaderPtr)sndPtr)->sampleSize == 8)
  993.                 {
  994.                     if (((ExtSoundHeaderPtr)sndPtr)->numChannels == 1)
  995.                         result = true;                    // it's a mono sound, no problem
  996.                     else
  997.                          result = HasStereoSupport();    // only if we have stereo
  998.                  }
  999.                  else
  1000.                      result = false;                        // non-8 bit not allowed
  1001.             }
  1002.             else        // must be a 16 bit sound, Sound Manager 3 and later does it all
  1003.                 result = true;
  1004.             break;
  1005.  
  1006.     }
  1007.     return (result);
  1008. }
  1009.  
  1010. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1011. /*
  1012. Given a channel and sampled sound resource, this routine will install the
  1013. sound into the channel for use as an instrument.  This allows an
  1014. application to send freqDurationCmds to the channel and play a melody.  If I sent
  1015. a bufferCmd instead of the soundCmd, the Sound Manager would play the
  1016. sampled sound.  This is basically what _SndPlay would do with a format 2
  1017. snd.  I insure that I am using only the proper buffer format having the
  1018. standard encode option.  If I were to support compressed sounds, I would
  1019. have to call the MACE synthesizers to expand the buffer before I can use
  1020. it as an instrument.  If I don't get a sampled sound of standard encoding
  1021. I'll return a bad format error.  I use _SndDoImmediate to get the sound
  1022. installed because I don't want this command to be queued.
  1023.  
  1024. VERSION 1.1:  Check for a supported sound header.
  1025. */
  1026.  
  1027. OSErr InstallSampleSnd(ChanInfoPtr info, SndListHandle sndHandle)
  1028. {
  1029.     SndCommand        theCmd;
  1030.     SoundHeaderPtr    dataPtr;
  1031.     long            dataOffset;
  1032.     short            sndDataType;
  1033.     short            ignore;
  1034.     OSErr            theErr;
  1035.  
  1036.     theErr = HoldSnd(sndHandle);
  1037.     if (theErr == noErr) {
  1038.         dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
  1039.         if (sndDataType == sampledSynth) {
  1040.             dataPtr = (SoundHeaderPtr)((long)(*sndHandle) + dataOffset);
  1041.             if (stdSH == dataPtr->encode) {
  1042.                 theCmd.cmd = soundCmd;
  1043.                 theCmd.param1 = 0;
  1044.                 theCmd.param2 = (long)dataPtr;
  1045.                 info->dataHandle = sndHandle;
  1046.                 theErr = SndDoImmediate(info->chan, &theCmd);
  1047.             } else
  1048.                 theErr = badFormat;                            //return a bad format error
  1049.         } else
  1050.             theErr = badFormat;                                //return a bad format error
  1051.         if (theErr != noErr) {
  1052.             HUnlock((Handle)sndHandle);                        //and free up the resource
  1053.             HPurge((Handle)sndHandle);
  1054.         }
  1055.     }
  1056.     return (theErr);
  1057. }
  1058.  
  1059. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1060. /*
  1061. This routine will create a sampled sound channel using the INIT option
  1062. given.  Typically this will be 0.  In any case with System 6.0x this
  1063. option is ignored by the sampled sound synthesizer.  The given sound
  1064. resource will be installed into the channel for use as an instrument.
  1065.  
  1066. WARNING: If the application does not want an instrument sound, then the
  1067. sndInstrument handle MUST be passed in as nil.
  1068.  
  1069. BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
  1070. a Memory Manager error when allocating its internal buffer.  There is a
  1071. call to NewPtr(1316)and if a nil is returned, the Sound Manager will
  1072. write randomly to low memory.  This can occur when calling _SysBeep under
  1073. low memory conditions.  Also, this pointer is allocated into the
  1074. application's heap instead of the system's.
  1075.  
  1076. VERSION 1.2: We no longer need to call ChanAvailable since we're only
  1077. supporting Sound Manager 2.0 or later.
  1078. */
  1079.  
  1080. #pragma segment SoundUnit
  1081. pascal OSErr GetSampleChan(SndChannelPtr *sampleChan, long init, SndListHandle sndInstrument)
  1082. {
  1083.     OSErr theErr;
  1084.  
  1085.     FreeAllChans();
  1086.     theErr = SndNewChannel(&(gChanInfo[0].chan), sampledSynth,
  1087.                             init, GetRoutineAddress(DoCallBack));
  1088.     if (theErr == noErr) {
  1089.         gChanInfo[0].chanType = sampledSynth;
  1090.         theErr = InstallSampleSnd(&gChanInfo[0], sndInstrument);
  1091.     }
  1092.     if (theErr != noErr)
  1093.         FreeAllChans();
  1094.     *sampleChan = gChanInfo[0].chan;
  1095.     return (theErr);
  1096. }
  1097.  
  1098. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1099. /*
  1100. Only supported by Sound Manager 2.0 and later
  1101.  
  1102. */
  1103.  
  1104. #pragma segment SoundUnit
  1105. pascal OSErr Get4SampleInstruments(SndChannelPtr *sampleChan1, SndChannelPtr *sampleChan2,
  1106.                                 SndChannelPtr *sampleChan3, SndChannelPtr *sampleChan4,
  1107.                                 SndListHandle sndInstrument1, SndListHandle sndInstrument2,
  1108.                                 SndListHandle sndInstrument3, SndListHandle sndInstrument4)
  1109. {
  1110.     OSErr theErr;
  1111.  
  1112.     FreeAllChans();
  1113.  
  1114.     theErr = SndNewChannel(&(gChanInfo[0].chan), sampledSynth,
  1115.                             kInitNone, GetRoutineAddress(DoCallBack));
  1116.     if (theErr == noErr) {
  1117.         gChanInfo[0].chanType = sampledSynth;
  1118.         theErr = InstallSampleSnd(&gChanInfo[0], sndInstrument1);
  1119.         if (theErr == noErr) {
  1120.             theErr = SndNewChannel(&(gChanInfo[1].chan), sampledSynth,
  1121.                                     kInitNone, GetRoutineAddress(DoCallBack));
  1122.             if (theErr == noErr) {
  1123.                 gChanInfo[1].chanType = sampledSynth;
  1124.                 theErr = InstallSampleSnd(&gChanInfo[1], sndInstrument2);
  1125.                 if (theErr == noErr) {
  1126.                     theErr = SndNewChannel(&(gChanInfo[2].chan), sampledSynth,
  1127.                                             kInitNone, GetRoutineAddress(DoCallBack));
  1128.                     if (theErr == noErr) {
  1129.                         gChanInfo[2].chanType = sampledSynth;
  1130.                         theErr = InstallSampleSnd(&gChanInfo[2], sndInstrument3);
  1131.                         if (theErr == noErr) {
  1132.                             theErr = SndNewChannel(&(gChanInfo[3].chan), sampledSynth,
  1133.                                                     kInitNone, GetRoutineAddress(DoCallBack));
  1134.                             if (theErr == noErr) {
  1135.                                 gChanInfo[3].chanType = sampledSynth;
  1136.                                 theErr = InstallSampleSnd(&gChanInfo[3], sndInstrument4);
  1137.                             }
  1138.                         }
  1139.                     }
  1140.                 }
  1141.             }
  1142.         }
  1143.     }
  1144.  
  1145.     if (theErr == noErr)
  1146.     {
  1147.         *sampleChan1 = gChanInfo[0].chan;
  1148.         *sampleChan2 = gChanInfo[1].chan;
  1149.         *sampleChan3 = gChanInfo[2].chan;
  1150.         *sampleChan4 = gChanInfo[3].chan;
  1151.     }
  1152.     else
  1153.         FreeAllChans();
  1154.     return (theErr);
  1155. }
  1156.  
  1157. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1158. /*
  1159. Given a channel and pointer to a wave table, this will install the wave
  1160. for use as an instrument into the channel.  If I find the application
  1161. giving me a nil pointer, I'll return an error.  I use _SndDoImmediate
  1162. to get the sound installed because I don't want this to be queued.
  1163. */
  1164.  
  1165. #pragma segment SoundUnit
  1166. pascal OSErr InstallWave(SndChannelPtr waveChan, Ptr aWavePtr, short waveLength)
  1167. {
  1168.     SndCommand theCmd;
  1169.     OSErr      result;
  1170.  
  1171.     if (aWavePtr != nil) {
  1172.         theCmd.cmd = waveTableCmd;
  1173.         theCmd.param1 = waveLength;
  1174.         theCmd.param2 = (long)aWavePtr;
  1175.         result = SndDoImmediate(waveChan, &theCmd);
  1176.     }
  1177.     else
  1178.         result = memPCErr;                        // Pointer Check failed
  1179.     return (result);
  1180. }
  1181.  
  1182. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1183. /*
  1184. NewWaveChan creates a new wave table channel and sets myChan to point
  1185. to it.  If any error occurs, the error code is returned as a function
  1186. result.
  1187. */
  1188.  
  1189. #pragma segment SoundUnit
  1190. OSErr NewWaveChan(ChanInfoPtr info, short init)
  1191. {
  1192.     OSErr theErr;
  1193.  
  1194.     theErr = SndNewChannel(&(info->chan), waveTableSynth,
  1195.                             init, GetRoutineAddress(DoCallBack));
  1196.     if (theErr == noErr)
  1197.         info->chanType = waveTableSynth;
  1198.  
  1199.     return (theErr);
  1200. }
  1201.  
  1202. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1203. /*
  1204. This will return four wave table channels with their waves installed.
  1205. This must be done before calling ChanAvailable.  Otherwise that test will
  1206. fail.  If I cannot obtain all four wave channels I will dispose of the
  1207. ones I did get before returning the error.  This routine expects to find
  1208. four wave table pointers, or it will fail.
  1209.  
  1210. Versions 1.2: After calling NewWaveChan, we do not need to call ChanAvailable
  1211. since we know that Sound Manager 2 or later will not do the wrong thing.
  1212. */
  1213.  
  1214. #pragma segment SoundUnit
  1215. pascal OSErr GetWaveChans(SndChannelPtr *waveChan1, SndChannelPtr *waveChan2,
  1216.                             SndChannelPtr *waveChan3, SndChannelPtr *waveChan4)
  1217. {
  1218.     OSErr theErr;
  1219.  
  1220.     FreeAllChans();
  1221.  
  1222.     theErr = NewWaveChan(&gChanInfo[0], initStereo);
  1223.     if (theErr == noErr)
  1224.     {
  1225.         theErr = NewWaveChan(&gChanInfo[1], initStereo);
  1226.         if (theErr == noErr)
  1227.         {
  1228.             theErr = NewWaveChan(&gChanInfo[2], initStereo);
  1229.             if (theErr == noErr)
  1230.                 theErr = NewWaveChan(&gChanInfo[3], initStereo);
  1231.         }
  1232.     }
  1233.  
  1234.     if (theErr != noErr)
  1235.         FreeAllChans();                            // we didn't make it
  1236.     else {
  1237.         *waveChan1 = gChanInfo[0].chan;
  1238.         *waveChan2 = gChanInfo[1].chan;
  1239.         *waveChan3 = gChanInfo[2].chan;
  1240.         *waveChan4 = gChanInfo[3].chan;
  1241.     }
  1242.     return (theErr);
  1243. }
  1244.  
  1245. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1246. /*
  1247. This will create a channel for the square wave synthesizer.  There
  1248. are no INIT options used by this synthesizer, but I will set the timbre
  1249. to adjust the tone quality.
  1250.  
  1251. VERSION 1.2: We no longer need to call ChanAvailable since we're only
  1252. supporting Sound Manager 2.0 or later.
  1253. */
  1254.  
  1255. #pragma segment Sound
  1256. pascal OSErr GetSquareWaveChan(SndChannelPtr *squareChan, short timbre)
  1257. {
  1258.     OSErr theErr;
  1259.  
  1260.     FreeAllChans();
  1261.     theErr = SndNewChannel(&(gChanInfo[0].chan), squareWaveSynth,
  1262.                                 kInitNone, GetRoutineAddress(DoCallBack));
  1263.     if (theErr == noErr) {
  1264.         gChanInfo[0].chanType = squareWaveSynth;
  1265.         theErr = SetSquareWaveTimbre(gChanInfo[0].chan, timbre, !kWait);
  1266.     }
  1267.     if (theErr != noErr)
  1268.         FreeAllChans();
  1269.     *squareChan = gChanInfo[0].chan;
  1270.     return (theErr);
  1271. }
  1272.  
  1273. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1274. /*
  1275. This is the routine to create a channel that isn't associated with any
  1276. synthesizer.  Why? Because if you wanted to use _SndPlay asynchronously
  1277. you need to get such a channel.
  1278.  
  1279. BUG NOTE: Do not use a channel already associated to a snth with
  1280. _SndPlay.  This causes the Sound Manager to install a second copy of the
  1281. same snth.
  1282.  
  1283. VERSION 1.2: We no longer need to call ChanAvailable since we're only
  1284. supporting Sound Manager 2.0 or later.
  1285. */
  1286.  
  1287. #pragma segment SoundUnit
  1288. pascal OSErr GetNoSynthChan(SndChannelPtr *chan)
  1289. {
  1290.     OSErr theErr;
  1291.  
  1292.     FreeAllChans();
  1293.     theErr = SndNewChannel(&(gChanInfo[0].chan), kNoSynth,
  1294.                                 kInitNone, GetRoutineAddress(DoCallBack));
  1295.     if (theErr == noErr)
  1296.         gChanInfo[0].chanType = kNoSynth;
  1297.     if (theErr != noErr)
  1298.         FreeAllChans();
  1299.     *chan = gChanInfo[0].chan;
  1300.     return (theErr);
  1301. }
  1302.  
  1303. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1304. /*
  1305. This routine will use the given channel and snd resource with _SndPlay.
  1306. This is used to play a sound, which is a series of sound commands commonly
  1307. referred to as a sequence.  First thing I do is make sure the song fits in
  1308. memory.  _SndPlay will lock this resource in memory and then pump the snd
  1309. for all of its worth.  I am calling it asynchronously, and if I was using
  1310. a snd that contained sound data I wouldn't mark the snd as being
  1311. purgeable.  But in this case, _SndPlay will be done with the snd as soon
  1312. as it returns because it copied all of the commands into the channel.
  1313. (There's no data associated with a sequence, just commands.) _SndPlay
  1314. will not return until it has done so.  After _SndPlay I need to work
  1315. around a bug in the freqDurationCmd.  The last thing to do is to send a
  1316. callBackCmd to signal me that the channel has completed.  If any Sound
  1317. Manager errors are encountered, I return them to the application.  If the
  1318. application passed me a nil snd handle, I'll return an error.
  1319.  
  1320. WARNING: Make sure you are using a snd that only has note type commands
  1321. in it and not something such as a bufferCmd.
  1322.  
  1323. BUG NOTE: There is problem when the final sound command is a freqDurationCmd.
  1324. The note will continue to sound, looping forever, until a quietCmd is sent
  1325. or the channel is disposed of.  To prevent unwanted looping, I send a
  1326. quietCmd after all notes.  Also read a related bug note when disposing of
  1327. channels in the routine FreeChan().
  1328. */
  1329.  
  1330. #pragma segment SoundUnit
  1331. pascal OSErr PlaySong(SndChannelPtr chan, SndListHandle sndSong)
  1332. {
  1333.     OSErr theErr;
  1334.  
  1335.     theErr = SndDataAvailable(sndSong);                // get the data loaded
  1336.     if (theErr == noErr) {
  1337.         theErr = SndPlay(chan, sndSong, kSMAsynch);    // pump the sound
  1338.         HUnlock((Handle)sndSong);
  1339.         HPurge((Handle)sndSong);
  1340.         if (theErr == noErr) {
  1341.             theErr = SendQuiet(chan, kWait);        // work around bug
  1342.             if (theErr == noErr)
  1343.                 theErr = SoundComplete(chan);
  1344.         }
  1345.         else
  1346.             theErr = nilHandleErr;                    // snd data was not available
  1347.     }
  1348.     if (theErr != noErr)
  1349.         FreeAllChans();
  1350.     return (theErr);
  1351. }
  1352.  
  1353. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1354. /*
  1355. This is used to send a syncCmd to a channel and causes the other channels
  1356. that are being held by a synchCmd to be released.  Of course, this assumes
  1357. the application has already called SynchChans.  _SndDoImmediate is used
  1358. to get the command directly to the synthesizer bypassing the queue.
  1359.  
  1360. BUG NOTE: I've found that immediately clearing the channels and starting
  1361. new ones may cause the channels to startup playing out of synch?  This
  1362. happens while disposing the wave channels and starting them immediately.
  1363. */
  1364.  
  1365. #pragma segment SoundUnit
  1366. OSErr ReleaseSynch(SndChannelPtr chan)
  1367. {
  1368.     SndCommand theCmd;
  1369.  
  1370.     theCmd.cmd = syncCmd;
  1371.     theCmd.param1 = 1;
  1372.     theCmd.param2 = kSyncID;
  1373.     return (SndDoImmediate(chan, &theCmd));
  1374. }
  1375.  
  1376. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1377. /*
  1378. This is a utility routine for SynchChans.  It sends a syncCmd command
  1379. to the channel specified by chan using the count parameter specified
  1380. by count.  Synch1Chan returns whatever error that SndDoImmediate returns.
  1381. */
  1382.  
  1383. #pragma segment SoundUnit
  1384. OSErr Synch1Chan(SndChannelPtr chan, short count)
  1385. {
  1386.     SndCommand theCmd;
  1387.  
  1388.     theCmd.cmd = syncCmd;
  1389.     theCmd.param1 = count;
  1390.     theCmd.param2 = kSyncID;
  1391.     return (SndDoImmediate(chan, &theCmd));
  1392. }
  1393.  
  1394. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1395. /*
  1396. This is used to synchronize four wave table channels.  By first sending
  1397. the synchCmd, I can send a sequence of other commands to the channel and
  1398. not have the channel attempt to start processing any of them.  That is until
  1399. another synchCmd is sent causing all of the previous synchCmd's counter
  1400. to be decremented.  After getting all the channels in synch and sending
  1401. the sequence of further commands, then use the ReleaseSynch routine to
  1402. start all of the channels processing their respective queues.
  1403. _SndDoImmediate is used to get the command directly to the synthesizer
  1404. bypassing the queue.
  1405. */
  1406.  
  1407. #pragma segment SoundUnit
  1408. OSErr SynchChans(SndChannelPtr chan1, SndChannelPtr chan2,
  1409.                         SndChannelPtr chan3, SndChannelPtr chan4)
  1410. {
  1411.     OSErr theErr;
  1412.  
  1413.     theErr = Synch1Chan(chan4, 5);
  1414.     if (theErr == noErr) {
  1415.         theErr = Synch1Chan(chan3, 4);
  1416.         if (theErr == noErr) {
  1417.             theErr = Synch1Chan(chan2, 3);
  1418.             if (theErr == noErr)
  1419.                 theErr = Synch1Chan(chan1, 2);
  1420.         }
  1421.     }
  1422.     return (theErr);
  1423. }
  1424.  
  1425. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1426. /*
  1427. In order to synchronize channels, the synchCmd is needed.  Once all of the
  1428. song has been sent into each channel, a final synchCmd is issued to
  1429. release them.  Don't send more commands into a channel that it can hold at
  1430. one time while the channel is in synch mode.
  1431. */
  1432.  
  1433. #pragma segment SoundUnit
  1434. pascal OSErr Play4ChanSongs(SndChannelPtr chan1, SndChannelPtr chan2,
  1435.                             SndChannelPtr chan3, SndChannelPtr chan4,
  1436.                             SndListHandle song1, SndListHandle song2,
  1437.                             SndListHandle song3, SndListHandle song4)
  1438. {
  1439.     OSErr theErr;
  1440.  
  1441.     theErr = SynchChans(chan1, chan2, chan3, chan4);
  1442.     if (theErr == noErr) {
  1443.         theErr = PlaySong(chan1, song1);
  1444.         if (theErr == noErr) {
  1445.             theErr = PlaySong(chan2, song2);
  1446.             if (theErr == noErr) {
  1447.                 theErr = PlaySong(chan3, song3);
  1448.                 if (theErr == noErr) {
  1449.                     theErr = PlaySong(chan4, song4);
  1450.                     if (theErr == noErr)
  1451.                         theErr = ReleaseSynch(chan1);
  1452.                 }
  1453.             }
  1454.         }
  1455.     }
  1456.  
  1457.     return (theErr);
  1458. }
  1459.  
  1460. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1461. /*
  1462. WARNING: IT IS RECOMMENDED THAT YOU DO NOT USE THIS CODE.  I've provided
  1463. this routine because people have asked me how HyperCard performs its PLAY
  1464. command and why their HyperCard sounds do not sound right using the
  1465. _SndPlay routine.  The correct answer is that _SndPlay plays the sound
  1466. correctly.  HyperCard is attempting to change the frequency by adjusting
  1467. the sample rate.  This is NOT the correct approach.  Define the sound
  1468. buffer as it should be played.  _SndPlay plays the sound as it is defined.
  1469. If the result from _SndPlay is not what you want, then it is the sample
  1470. that is incorrect and should be edited.  The sample rate is the rate at
  1471. which the sound was recorded.  If you didn't record it, then how do you
  1472. know what's the correct rate?  Set the baseFrequency to the note that was
  1473. recorded.  If you recorded middle C at 22k, then the rate is 22k and the
  1474. baseFrequency is middle C.  HyperCard is incorrect in using the sample rate as
  1475. the frequency of the sound.  Furthermore, using this technique of
  1476. calculating a new sample rate can introduce errors.  The resulting sample
  1477. rate will not be the proper pitch.  Also, the sample rate for high pitches
  1478. will be very inaccurate and impossible for the Mac to reproduce.  Such a
  1479. problem can happen if the given sample rate was 22k and is to be played
  1480. back at three octaves higher.  Even 44k samples transposed up a half
  1481. octave will fail.  Using the soundCmd and freqDurationCmd will not have this
  1482. problem.
  1483.  
  1484. Given a sound resource, this routine will play it in the manner that
  1485. HyperCard does.  HyperCard assumes that a sound is to be played at middle
  1486. C when the user does not specify a note value in the PLAY command.  I
  1487. don't know why. (What's middle C when I want to hear speech or the sound
  1488. of crickets?) At any rate(pun intended), I get a sampled sound channel.
  1489. I find the sound data offset in the resource, which has to be locked down
  1490. at this time.  Once I have the sound data, I get its original sample rate.
  1491. I have to calculate what a new sample rate would be based on its baseFrequency.
  1492. The baseFrequency is the note at which the sound was recorded.  I'm not sure
  1493. what this means to crickets, but if this is set to middle C then HyperCard
  1494. doesn't attempt to modify the sample rate. (If you're wondering how the
  1495. math works in this routine, buy a book on music theory.  I'm here to
  1496. provide Mac support.) Once I've adjusted the sample rate, I use the
  1497. bufferCmd to play it.  Then I restore the sound resource to its original
  1498. state.  If I didn't do this it would be possible that the resource was
  1499. still in memory the next time I use it having the adjusted sample rate.
  1500. This would cause me to incorrectly adjust it again.  Unlike HyperCard, I
  1501. can do this for both a format 1 and 2.
  1502.  
  1503. BUG NOTE: Do not call SANE while the Sound Manager is running.  Refer to
  1504. Tech Note #235.
  1505.  
  1506. VERSION 1.1:  Replaced the previous test of the sound header's encode
  1507. value.  The previous version was incorrect.  The bufferCmd will automatically
  1508. de-code a MACE compressed sound if MACE is available.  Otherwise, the sound
  1509. will not be able to be used.  So, a new routine is being used to check for
  1510. supported sound headers.  The conflict with the Sound Manager and SANE was
  1511. resolved in the new Sound Manager.  The Sound Manager no longers uses extended
  1512. numbers at interrupt level, and instead uses fixed math.
  1513. */
  1514.  
  1515. #pragma segment SoundUnit
  1516. pascal OSErr HyperSndPlay(SndListHandle sndHandle)
  1517. {
  1518.     SndCommand      theCmd;
  1519.     OSErr              theErr;
  1520.     long             dataOffset;
  1521.     short             sndDataType;
  1522.     short              ignore;
  1523.     SoundHeaderPtr    dataPtr;
  1524.     short              thePower;
  1525.     double_t        newRate;
  1526.     Fixed           oldRate;
  1527.  
  1528.     theErr = HoldSnd(sndHandle);
  1529.     if (theErr == noErr) {
  1530.         theErr = GetSampleChan(&(gChanInfo[0].chan), kInitNone, nil);
  1531.         gChanInfo[0].dataHandle = sndHandle;                  // so FreeAllChans can dispose of data
  1532.         if (theErr == noErr) {
  1533.             dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
  1534.             if (sndDataType == sampledSynth) {
  1535.                 dataPtr = (SoundHeaderPtr)((long)*sndHandle + dataOffset);
  1536.  
  1537.                 if (SupportedSH(dataPtr)) {
  1538.                     oldRate = dataPtr->sampleRate;      // save original sample rate
  1539.                     if (dataPtr->baseFrequency != kMiddleC) {
  1540.                         if (dataPtr->sampleRate > (SHRT_MAX << 16)) // large positive number
  1541.                             newRate = Fix2X(dataPtr->sampleRate - (SHRT_MAX << 16))
  1542.                                             + (double_t)SHRT_MAX;
  1543.                         else
  1544.                             newRate = Fix2X(dataPtr->sampleRate);
  1545.                         thePower = (kMiddleC) - (dataPtr->baseFrequency);
  1546.                         dataPtr->sampleRate = X2Fix(newRate *
  1547.                                             pow((double_t)twelfthRootTwo, (double_t)thePower));
  1548.                     }
  1549.                     theCmd.cmd = bufferCmd;
  1550.                     theCmd.param1 = 0;
  1551.                     theCmd.param2 = (long)dataPtr;
  1552.                     theErr = SndDoImmediate(gChanInfo[0].chan, &theCmd);
  1553.                     if (theErr == noErr)
  1554.                         theErr = SoundComplete(gChanInfo[0].chan);
  1555.                     dataPtr->sampleRate = oldRate;      // restore original sample rate
  1556.                 } else
  1557.                     theErr = badFormat;                    //not SupportedSH
  1558.             } else
  1559.                 theErr = badFormat;                        //sndDataType not sampledSynth
  1560.         }
  1561.     }
  1562.     if (theErr != noErr)
  1563.         FreeAllChans();
  1564.     return (theErr);
  1565. }
  1566.  
  1567. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1568. /*
  1569. Given a sound resource, this routine will call _SndPlay.  The snd must
  1570. be either a format 2 or format 1 that contains snth information.
  1571. Using _SndPlay asynchronously requires us to lock the snd prior to
  1572. calling the trap.  The reason being is _SndPlay calls HSetState as soon
  1573. as the trap exits, and restores the handle to whatever is was just before
  1574. the call.  This would be bad when using the sound asynchronously.  If the
  1575. sound being passed in happens to be a compressed sound created with MACE,
  1576. it will "do the right thing."  If MACE isn't around the Sound Manager
  1577. will pretend to play a sound but nothing will be heard.
  1578.  
  1579. BUG NOTE:  The sampled sound synthesizer in System 6.0x does not check for
  1580. a Memory Manager error when allocating its internal buffer.  There is a
  1581. call to NewPtr(1316)and if a nil is return, the Sound Manager will write
  1582. randomly to memory.  Also, the pointer is allocated into the application's
  1583. heap instead of the system's.
  1584.  
  1585. BUG NOTE:  _SndPlay when using System 6.0.4 and a sampled sound will send
  1586. a bogus callBackCmd into the channel.  This will cause the user's call
  1587. back procedure to be called as soon as the sound has completed.  Refer
  1588. to the DoCallBack routine for details.
  1589.  
  1590. VERSION 1.1:  Add the check for the sound header being supported and
  1591. replaced the check of the snth information.  No synth information in the
  1592. snd is valid and would mean to play the snd using the squareWaveSynth.
  1593. */
  1594.  
  1595. #pragma segment SoundUnit
  1596. pascal OSErr AsynchSndPlay(SndListHandle sndHandle)
  1597. {
  1598.     SoundHeaderPtr dataPtr;
  1599.     OSErr theErr;
  1600.     long dataOffset;
  1601.     short sndDataType;
  1602.     short ignore;
  1603.  
  1604.     theErr = HoldSnd(sndHandle);                //hold on to the sound
  1605.     if (theErr == noErr) {
  1606.         theErr = GetNoSynthChan(&(gChanInfo[0].chan));
  1607.         gChanInfo[0].dataHandle = sndHandle;        //so FreeAllChans can dispose of data
  1608.         if (theErr == noErr) {
  1609.             gChanInfo[0].chanType = GetSynthInfo(sndHandle).modNumber;
  1610.             dataOffset = GetSndDataOffset(sndHandle, &sndDataType, &ignore);
  1611.             if (sndDataType == sampledSynth) {
  1612.                 dataPtr = (SoundHeaderPtr)((long)*sndHandle + dataOffset);
  1613.                 if ( !(SupportedSH(dataPtr)) )
  1614.                     theErr = badFormat;
  1615.             }
  1616.             if (theErr == noErr) {
  1617.                 theErr = SndPlay(gChanInfo[0].chan, sndHandle, kSMAsynch);
  1618.                 if (theErr == noErr)
  1619.                     theErr = SoundComplete(gChanInfo[0].chan);
  1620.             }
  1621.         }
  1622.     }
  1623.     if (theErr != noErr)
  1624.         FreeAllChans();
  1625.     return(theErr);
  1626. }
  1627.  
  1628.